// scalaxx - Scala XML eXtensions
// (c) 2009, Normen Müller, normen.mueller@gmail.com
// $Id: implicits.scala 50 2009-11-12 11:37:31Z normen.mueller $
package scalaxx

import scala.xml._

/** The object <code>Views</code> provides implicit views to Scala XML eXtensions.
 *  Using <code>scalaxx</code> one should import <code>scalaxx.Views._</code> at the very beginning of each unit.
 * 
 *  <p>@thanks to Jamie Webb &lt;jwebb@scalaforge.org&gt; for his initial ideas on the scalaxx library
 *  (v0.0.2) by his scalax library (v0.1).</p>
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2*/
object Views {
  implicit def stringExtras(string: String) = new StringExtras(string)
  
  implicit def xmlNodeExtras(n: scala.xml.Node) = new NodeExtras(n)
  implicit def xmlMetadataExtras(m: scala.xml.MetaData) = new MetaDataExtras(m)
  implicit def xmlNamespaceExtras(n: scala.xml.NamespaceBinding) = new NamespaceBindingExtras(n)
}
import Views._

private [scalaxx] class StringExtras(string: String) {  
  /** Constructs a new XPath <code>Expr</code> by parsing the enclosed string <code>string</code>. */
  lazy val toXPath = path.XPath.parse(string)
}

/**
 * @param node to enclose/ extend
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [scalaxx] class NodeExtras(node: Node) {  
  /** Extended QName */
  lazy val qname = if (node.prefix != null) node.namespace + ":" + node.label else node.label
  
//  def ?(xp: XPath) = error("not yet implemented")
  
  /** @todo document */
  def \@(a: String): String = node attribute a match { case Some(v) => v.text case _ => "" }
  
  /** @todo document
   @thanks git://gist.github.com/167566.git */
  def toJdkNode(doc: org.w3c.dom.Document): org.w3c.dom.Node = node match {
    case Elem(prefix,label,attributes,scope,children@ _*) => ((r: org.w3c.dom.Element) => {
      //@todo ns
      for (a <- attributes) r.setAttribute(a.key, a.value.text)
      for (c <- children) r.appendChild(c.toJdkNode(doc))
      r
    })(doc.createElement(label))
    case Text(text) => doc.createTextNode(text)
    case Comment(comment) => doc.createComment(comment)
    // not sure
    case a: Atom[_] => doc.createTextNode(a.data.toString)
    // XXX: other types
    //case x => throw new Exception(x.getClass.getName)
  }
}

private [scalaxx] class ElemExtras(elem: Elem) extends NodeExtras(elem) {
  private val docBuilder = javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder()
  
  override def toJdkNode(doc: org.w3c.dom.Document) = super.toJdkNode(doc).asInstanceOf[org.w3c.dom.Element]
  
  /** @todo document
   @thanks git://gist.github.com/167566.git */
  def toJdkDoc = {
    val doc = docBuilder.newDocument()
    doc.appendChild(toJdkNode(doc))
    doc
  }
}

/** Note: unprefixed attributes have no namespace (cf. http://www.w3.org/TR/REC-xml-names/#scoping-defaulting)
 * 
 * @param attribute to enclose/ extend
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [scalaxx] class MetaDataExtras(attribute: MetaData) {
  /** Extended QName */
  def qname(n: Node) = attribute match {
    case m: PrefixedAttribute   => m.getNamespace(n) + ":" + m.key
    case m: UnprefixedAttribute => m.key
  }
  
  /* Note, that this is fix for MetaData#containedIn1 */
  def containedIn(m: MetaData): Boolean = m.equals1(attribute) || (m.hasNext && containedIn(m.next))
  
  /* Note: The position refers to internal position, not to the original positon
   * 
   * scala> <test a="A" b="B" c="C"/>
   * res0: scala.xml.Elem = <test a="A" c="C" b="B"></test>
   * 
   * scala> <test c="C" b="B" a="A"/>
   * res1: scala.xml.Elem = <test a="A" c="C" b="B"></test> */
  def positionOf(m: MetaData) = attribute.elements findIndexOf (_ equals1 m) match {case -1 => -1 case z@_ => z + 1}
}

/**
 * @param scope to enclose/ extend
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [scalaxx] class NamespaceBindingExtras(scope: NamespaceBinding) extends Iterable[NamespaceBinding] {  
  def elements: Iterator[NamespaceBinding] = new Iterator[NamespaceBinding] {
    var x: NamespaceBinding = scope
    def hasNext = x != TopScope
    def next = {
      val y = x
      x = x.parent
      y
    }
  }
}
